package ginsession

import (
    "bytes"
    "context"
    "encoding/base32"
    "encoding/gob"
    "net/http"
    "strings"
    "time"

    "github.com/go-redis/redis/v8"
    "github.com/gorilla/securecookie"
    "github.com/gorilla/sessions"
)

func unmarshal(data []byte, v interface{}) error {
    dec := gob.NewDecoder(bytes.NewBuffer(data))
    return dec.Decode(v)
}

func marshal(v interface{}) ([]byte, error) {
    b := &bytes.Buffer{}
    enc := gob.NewEncoder(b)
    err := enc.Encode(v)
    if err != nil {
        return nil, err
    } else {
        return b.Bytes(), nil
    }
}

func NewSessionStore(keyPairs ...[]byte) *RediSessiontore {
    store := RediSessiontore{
        Codecs: securecookie.CodecsFromPairs(keyPairs...),
        Options: &sessions.Options{
            Path:   "/",
            MaxAge: 86400 * 30,
        },
    }
    return &store
}

type RediSessiontore struct {
    rds           *redis.Client
    Codecs        []securecookie.Codec
    Options       *sessions.Options
    DefaultMaxAge int
    keyPrefix     string
}

func (s *RediSessiontore) WithRedisKeyPrefix(str string) *RediSessiontore {
    s.keyPrefix = str
    return s
}

func (s *RediSessiontore) WithRedis(rds *redis.Client) *RediSessiontore {
    s.rds = rds
    return s
}

func (s *RediSessiontore) redis_key(ses *sessions.Session) string {
    return s.keyPrefix + ses.ID
}

func (s *RediSessiontore) Get(r *http.Request, name string) (*sessions.Session, error) {
    return sessions.GetRegistry(r).Get(s, name)
}

func (s *RediSessiontore) New(r *http.Request, name string) (*sessions.Session, error) {
    session := sessions.NewSession(s, name)
    options := *s.Options
    session.Options = &options
    session.IsNew = true

    c, err := r.Cookie(name)
    if err != nil {

        return session, nil
    }

    err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
    if err != nil {

        return session, err
    }

    buf, err := s.rds.Get(context.Background(), s.redis_key(session)).Bytes()
    if err != nil {

        return session, nil
    }

    v := make(map[interface{}]interface{})
    if err := unmarshal(buf, &v); err != nil {

        return session, nil
    }

    session.Values = v
    session.IsNew = false
    return session, nil
}

func (s *RediSessiontore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
    if session.Options.MaxAge < 0 {

        for k := range session.Values {
            delete(session.Values, k)
        }

        if _, err := s.rds.Del(context.Background(), s.redis_key(session)).Result(); err != nil {
            return err
        }

        http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
    } else {

        if session.ID == "" {

            session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
        }
        session_id, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
        if err != nil {
            return err
        }

        buf, err := marshal(session.Values)
        if err != nil {
            return err
        }

        age := session.Options.MaxAge
        if age == 0 {
            age = s.DefaultMaxAge
        }
        if _, err := s.rds.Set(context.Background(),
            s.redis_key(session), buf,
            time.Duration(age)*time.Second).Result(); err != nil {
            return err
        }

        http.SetCookie(w, sessions.NewCookie(session.Name(), session_id, session.Options))
    }
    return nil
}

func (s *RediSessiontore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {

    return nil
}
